﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using CodeLibrary.Consts;

namespace CodeLibrary.Extensions
{
    public static class StringExtension
    {
        public static bool IsNullOrEmpty(this string s)
        {
            return string.IsNullOrEmpty(s);
        }

        public static bool IsMatch(this string s, string pattern)
        {
            if (s == null)
            {
                return false;
            }
            return Regex.IsMatch(s, pattern);
        }

        public static string Match(this string s, string pattern)
        {
            if (s == null)
            {
                return "";
            }
            return Regex.Match(s, pattern).Value;
        }

        public static string Format(this string format, params object[] args)
        {
            return string.Format(format, args);
        }

        public static bool IsInt(this string s)
        {
            int i;
            return int.TryParse(s, out i);
        }

        public static int ConvertToInt(this string s)
        {
            return int.Parse(s);
        }

        public static string CaseInsenstiveReplace(this string val, char charToReplace, char replacement)
        {
            Regex regEx = new Regex(charToReplace.ToString(), RegexOptions.IgnoreCase | RegexOptions.Multiline);
            return regEx.Replace(val, replacement.ToString());
        }
        
        public static string CaseInsenstiveReplace(this string val, string stringToReplace, string replacement)
        {
            Regex regEx = new Regex(stringToReplace, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            return regEx.Replace(val, replacement);
        }
        
        public static string ReplaceFirstMatch(this string val, string stringToReplace, string replacement)
        {
            Regex regEx = new Regex(stringToReplace, RegexOptions.Multiline);
            return regEx.Replace(val, replacement, 1);
        }

        public static string ReplaceFirstMatch(this string val, char charToReplace, char replacement)
        {
            Regex regEx = new Regex(charToReplace.ToString(), RegexOptions.Multiline);
            return regEx.Replace(val, replacement.ToString(), 1);
        }

        public static string ReplaceLastMatch(this string val, char charToReplace, char replacement)
        {
            int index = val.LastIndexOf(charToReplace);
            if (index < 0)
            {
                return val;
            }
            StringBuilder sb = new StringBuilder(val.Length - 2);
            sb.Append(val.Substring(0, index));
            sb.Append(replacement);
            sb.Append(val.Substring(index + 1,
                                    val.Length - index - 1));
            return sb.ToString();
        }

        public static string ReplaceLastMatch(this string val, string stringToReplace, string replacement)
        {
            int index = val.LastIndexOf(stringToReplace, StringComparison.Ordinal);
            if (index < 0)
            {
                return val;
            }
            StringBuilder sb = new StringBuilder(val.Length - stringToReplace.Length + replacement.Length);
            sb.Append(val.Substring(0, index));
            sb.Append(replacement);
            sb.Append(val.Substring(index + stringToReplace.Length,
                                    val.Length - index - stringToReplace.Length));

            return sb.ToString();
        }
        
        public static string RemoveWords(this string val, params string[] filterWords)
        {
            return MaskWords(val, char.MinValue, filterWords);
        }
        
        public static string MaskWords(this string val, char mask, params string[] filterWords)
        {
            string stringMask = (mask == char.MinValue ? string.Empty : mask.ToString());
            string totalMask = stringMask;

            foreach (string s in filterWords)
            {
                Regex regEx = new Regex(s, RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if (stringMask.Length > 0)
                {
                    for (int i = 1; i < s.Length; i++)
                        totalMask += stringMask;
                }

                val = regEx.Replace(val, totalMask);
                totalMask = stringMask;
            }
            return val;
        }
        
        public static string PadLeft(this string val, string pad, int totalWidth)
        {
            return PadLeft(val, pad, totalWidth, false);
        }
        
        public static string PadLeft(this string val, string pad, int totalWidth, bool cutOff)
        {
            if (val.Length >= totalWidth)
                return val;

            string paddedString = val;

            while (paddedString.Length < totalWidth)
            {
                paddedString += pad;
            }

            if (cutOff)
                paddedString = paddedString.Substring(0, totalWidth);
            return paddedString;
        }
        
        public static string PadRight(this string val, string pad, int totalWidth)
        {
            return PadRight(val, pad, totalWidth, false);
        }
        
        public static string PadRight(this string val, string pad, int totalWidth, bool cutOff)
        {
            if (val.Length >= totalWidth)
                return val;

            string paddedString = string.Empty;

            while (paddedString.Length < totalWidth - val.Length)
            {
                paddedString += pad;
            }

            if (cutOff)
                paddedString = paddedString.Substring(0, totalWidth - val.Length);
            paddedString += val;
            return paddedString;
        }
        
        public static string RemoveNewLines(this string val)
        {
            return RemoveNewLines(val, false);
        }
        
        public static string RemoveNewLines(this string input, bool addSpace)
        {
            string replace = addSpace ? " " : string.Empty;
            const string pattern = @"[\r|\n]";
            Regex regEx = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            return regEx.Replace(input, replace);
        }
        
        public static string RemoveNonNumeric(this string s)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char t in s)
            {
                if (Char.IsNumber(t))
                    sb.Append(t);
            }
            return sb.ToString();
        }
        
        public static string RemoveNumeric(this string s)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < s.Length; i++)
            {
                if (!Char.IsNumber(s[i]))
                    sb.Append(s[i]);
            }
            return sb.ToString();
        }
        
        public static string Reverse(this string val)
        {
            char[] reverse = new char[val.Length];
            for (int i = 0, k = val.Length - 1; i < val.Length; i++, k--)
            {
                if (char.IsSurrogate(val[k]))
                {
                    reverse[i + 1] = val[k--];
                    reverse[i++] = val[k];
                }
                else
                {
                    reverse[i] = val[k];
                }
            }
            return new string(reverse);
        }
        
        public static string SentenceCase(this string val)
        {
            if (val.Length < 1)
                return val;

            string sentence = val.ToLower();
            return sentence[0].ToString().ToUpper() +
               sentence.Substring(1);
        }
        
        public static string TitleCase(this string val)
        {
            if (val.Length == 0) return string.Empty;
            return TitleCase(val, true);
        }
        
        public static string TitleCase(this string val, bool ignoreShortWords)
        {
            if (val.Length == 0) return string.Empty;

            IList<string> ignoreWords = null;
            if (ignoreShortWords)
            {
                //TODO: Add more ignore words?
                ignoreWords = new List<string> {"a", "is", "was", "the"};
            }

            string[] tokens = val.Split(' ');
            StringBuilder sb = new StringBuilder(val.Length);
            foreach (string s in tokens)
            {
                if (ignoreShortWords
                    && s != tokens[0]
                    && ignoreWords.Contains(s.ToLower()))
                {
                    sb.Append(s + " ");
                }
                else
                {
                    sb.Append(s[0].ToString().ToUpper());
                    sb.Append(s.Substring(1).ToLower());
                    sb.Append(" ");
                }
            }

            return sb.ToString().Trim();
        }
        
        public static string TrimIntraWords(this string val)
        {
            Regex regEx = new Regex(@"[\s]+");
            return regEx.Replace(val, " ");
        }
        
        public static string WordWrap(this string val, int charCount)
        {
            return WordWrap(val, charCount, false, Environment.NewLine);
        }
        
        public static string WordWrap(this string val, int charCount, bool cutOff)
        {
            return WordWrap(val, charCount, cutOff, Environment.NewLine);
        }
        private static string WordWrap(this string val, int charCount, bool cutOff, string breakText)
        {
            StringBuilder sb = new StringBuilder(val.Length + 100);
            int counter = 0;

            if (cutOff)
            {
                while (counter < val.Length)
                {
                    if (val.Length > counter + charCount)
                    {
                        sb.Append(val.Substring(counter, charCount));
                        sb.Append(breakText);
                    }
                    else
                    {
                        sb.Append(val.Substring(counter));
                    }
                    counter += charCount;
                }
            }
            else
            {
                string[] strings = val.Split(' ');
                for (int i = 0; i < strings.Length; i++)
                {
                    counter += strings[i].Length + 1;
                    if (i != 0 && counter > charCount)
                    {
                        sb.Append(breakText);
                        counter = 0;
                    }

                    sb.Append(strings[i] + ' ');
                }
            }
            return sb.ToString().TrimEnd();
        }
        
        public static string ToCSV(this IEnumerable<string> val, bool insertSpaces)
        {
            if (insertSpaces)
                return String.Join(", ", val.ToArray());
            return String.Join(",", val.ToArray());
        }

        public static string ToCSV(this IEnumerable<char> val, bool insertSpaces)
        {
            List<string> casted = new List<string>();
            foreach (var item in val)
                casted.Add(item.ToString());

            if (insertSpaces)
                return String.Join(", ", casted.ToArray());
            return String.Join(",", casted.ToArray());
        }

        public static IEnumerable<string> ListFromCSV(this string val)
        {
            string[] split = val.Split(',');
            for (int i = 0; i < split.Length; i++)
            {
                split[i] = split[i].Trim();
            }
            return new List<string>(split);
        }
        
        public static void Serialize(this string val, string filePath)
        {
            Stream stream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, val);
            stream.Close();
        }

        public static string Base64StringEncode(this string val)
        {
            byte[] toEncodeAsBytes = Encoding.ASCII.GetBytes(val);
            string returnValue = Convert.ToBase64String(toEncodeAsBytes);
            return returnValue;
        }
        
        public static string Base64StringDecode(this string val)
        {
            byte[] encodedDataAsBytes = Convert.FromBase64String(val);
            string returnValue = Encoding.ASCII.GetString(encodedDataAsBytes);
            return returnValue;
        }
        
        public static string PadLeftHtmlSpaces(this string val, int totalSpaces)
        {
            return PadLeft(val, HTMLEscapeSequenceConsts.Space, 
                val.Length + (totalSpaces * HTMLEscapeSequenceConsts.Space.Length));
        }

        public static string PadRightHtmlSpaces(this string val, int totalSpaces)
        {
            return PadRight(val, HTMLEscapeSequenceConsts.Space, 
                val.Length + (totalSpaces * HTMLEscapeSequenceConsts.Space.Length));
        }

        public static string HtmlSpecialEntitiesEncode(this string val)
        {
            return HttpUtility.HtmlEncode(val);
        }
        
        public static string HtmlSpecialEntitiesDecode(this string val)
        {
            return HttpUtility.HtmlDecode(val);
        }
        
        public static string MD5String(this string val)
        {
            MD5 md5Hasher = MD5.Create();
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(val));

            StringBuilder sBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }
            return sBuilder.ToString();
        }
        
        public static bool VerifyMD5String(this string val, string hash)
        {
            string hashOfInput = MD5String(val);
            StringComparer comparer = StringComparer.OrdinalIgnoreCase;
            return 0 == comparer.Compare(hashOfInput, hash);
        }
        
        public static string SpaceToNbsp(this string val)
        {
            return val.Replace(" ", HTMLEscapeSequenceConsts.Space);
        }

        public static string StripTags(this string val)
        {
            Regex stripTags = new Regex("<(.|\n)+?>");
            return stripTags.Replace(val, "");
        }
        
        public static string NewLineToBreak(this string val)
        {
            Regex regEx = new Regex(@"[\n|\r]+");
            return regEx.Replace(val, "<br/>");
        }

        public static bool IsValidEmail(this string val)
        {
            if (string.IsNullOrEmpty(val)) return false;

            const string expresion = @"^(?:[a-zA-Z0-9_'^&/+-])+(?:\.(?:[a-zA-Z0-9_'^&/+-])+)*@(?:(?:\[?(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\.){3}(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\]?)|(?:[a-zA-Z0-9-]+\.)+(?:[a-zA-Z]){2,}\.?)$";
            Regex regex = new Regex(expresion, RegexOptions.IgnoreCase);
            return regex.IsMatch(val);
        }
        
        public static bool IsValidURI(this string val)
        {
            if (string.IsNullOrEmpty(val)) return false;

            return Uri.IsWellFormedUriString(val, UriKind.Absolute);
        }
        
        public static bool IsValidIP(this string val)
        {
            if (string.IsNullOrEmpty(val)) return false;

            const string strIPv4Pattern = @"\A(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z";
            const string strIPv6Pattern = @"\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\z";
            const string strIPv6Pattern_HEXCompressed = @"\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\z";
            const string StrIPv6Pattern_6Hex4Dec = @"\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z";
            const string StrIPv6Pattern_Hex4DecCompressed = @"\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z";
            Regex checkstrIPv4Pattern = new Regex(strIPv4Pattern);
            Regex checkstrIPv6Pattern = new Regex(strIPv6Pattern);
            Regex checkstrIPv6Pattern_HEXCompressed = new Regex(strIPv6Pattern_HEXCompressed);
            Regex checkStrIPv6Pattern_6Hex4Dec = new Regex(StrIPv6Pattern_6Hex4Dec);
            Regex checkStrIPv6Pattern_Hex4DecCompressed = new Regex(StrIPv6Pattern_Hex4DecCompressed);
            return checkstrIPv4Pattern.IsMatch(val, 0) ||
                checkstrIPv6Pattern.IsMatch(val, 0) ||
                checkstrIPv6Pattern_HEXCompressed.IsMatch(val, 0) ||
                checkStrIPv6Pattern_6Hex4Dec.IsMatch(val, 0) ||
                checkStrIPv6Pattern_Hex4DecCompressed.IsMatch(val, 0);
        }
        
        public static bool IsPalindrome(this string val)
        {
            if (val.Length == 0) return false;
            return val.ToLower() == val.Reverse().ToLower();
        }

    }
}
